#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project ：V2 
@File    ：cplex.py
@IDE     ：PyCharm 
@Author  ：郭星
@Date    ：2025/10/22 22:17 
'''
from DSB.SWT1.Code1.algorithm_comparison import BaseSchedulingAlgorithm

try:
    import cplex
    from cplex.exceptions import CplexError
except ImportError:
    print("警告：未安装CPLEX，相关功能无法使用")
    cplex = None


class CplexSchedulingAlgorithm(BaseSchedulingAlgorithm):
    """基于CPLEX的精确调度算法"""

    def __init__(self, instance: Dict, time_limit: int = 300, name: str = "CPLEX精确算法"):
        if cplex is None:
            raise RuntimeError("CPLEX未安装，无法使用该算法")

        super().__init__(instance, name=name)
        self.time_limit = time_limit  # 求解时间限制（秒）
        self.model = None
        self.vars = {}  # 存储模型变量

    def solve(self):
        """构建并求解CPLEX模型"""
        start_time = time.time()

        try:
            # 1. 初始化模型
            self.model = cplex.Cplex()
            self.model.set_results_stream(None)  # 关闭结果输出
            self.model.set_warning_stream(None)  # 关闭警告输出
            self.model.parameters.timelimit.set(self.time_limit)  # 设置时间限制

            # 2. 定义变量
            self._define_variables()

            # 3. 添加约束
            self._add_constraints()

            # 4. 设置目标函数（最小化总完工时间）
            self._set_objective()

            # 5. 求解模型
            self.model.solve()

            # 6. 提取最优解
            self._extract_solution()

        except CplexError as e:
            print(f"CPLEX求解出错: {e}")
        finally:
            self.execution_time = time.time() - start_time

    def _define_variables(self):
        """定义模型变量"""
        # 时间变量: 各阶段开始/结束时间
        # 清洗阶段结束时间
        self.vars["clean_end"] = self.model.variables.add(
            names=[f"clean_end_{wp}" for wp in range(self.num_workpieces)],
            lb=[0.0] * self.num_workpieces
        )

        # 充装阶段各组分结束时间
        self.vars["inflate_end"] = {}
        for wp in range(self.num_workpieces):
            for comp in range(self.num_components):
                self.vars["inflate_end"][(wp, comp)] = self.model.variables.add(
                    names=[f"inflate_end_{wp}_{comp}"],
                    lb=[0.0]
                )[0]

        # 摇匀阶段结束时间
        self.vars["shake_end"] = self.model.variables.add(
            names=[f"shake_end_{wp}" for wp in range(self.num_workpieces)],
            lb=[0.0] * self.num_workpieces
        )

        # 分析阶段结束时间
        self.vars["analyze_end"] = self.model.variables.add(
            names=[f"analyze_end_{wp}" for wp in range(self.num_workpieces)],
            lb=[0.0] * self.num_workpieces
        )

        # 设备分配二进制变量: x[wp][eq] = 1表示工件wp在清洗设备eq上加工
        self.vars["clean_eq"] = {}
        for wp in range(self.num_workpieces):
            for eq in range(self.num_clean_eq):
                self.vars["clean_eq"][(wp, eq)] = self.model.variables.add(
                    names=[f"clean_eq_{wp}_{eq}"],
                    types=[self.model.variables.type.binary]
                )[0]

        # 充装设备分配变量
        self.vars["inflate_eq"] = {}
        for wp in range(self.num_workpieces):
            for comp in range(self.num_components):
                for eq in range(self.num_inflation_eq):
                    self.vars["inflate_eq"][(wp, comp, eq)] = self.model.variables.add(
                        names=[f"inflate_eq_{wp}_{comp}_{eq}"],
                        types=[self.model.variables.type.binary]
                    )[0]

        # 总完工时间
        self.vars["makespan"] = self.model.variables.add(
            names=["makespan"],
            lb=[0.0]
        )[0]

    def _add_constraints(self):
        """添加约束条件"""
        # 1. 清洗阶段约束
        # 每个工件只能在一个清洗设备上加工
        for wp in range(self.num_workpieces):
            self.model.linear_constraints.add(
                lin_expr=[[
                    [self.vars["clean_eq"][(wp, eq)] for eq in range(self.num_clean_eq)],
                    [1.0] * self.num_clean_eq
                ]],
                senses=["E"],
                rhs=[1.0],
                names=[f"clean_eq_unique_{wp}"]
            )

        # 清洗时间约束: 结束时间 = 开始时间 + 清洗时间
        # 简化模型：假设开始时间为0或前一个任务结束时间（此处使用大M约束处理设备冲突）
        M = 1e6  # 大M常数
        for eq in range(self.num_clean_eq):
            for wp1 in range(self.num_workpieces):
                for wp2 in range(self.num_workpieces):
                    if wp1 != wp2:
                        self.model.linear_constraints.add(
                            lin_expr=[[
                                [self.vars["clean_end"][wp1], self.vars["clean_end"][wp2],
                                 self.vars["clean_eq"][(wp1, eq)], self.vars["clean_eq"][(wp2, eq)]],
                                [1.0, -1.0, M, -M]
                            ]],
                            senses=["G"],
                            rhs=[self.clean_time],
                            names=[f"clean_conflict_{eq}_{wp1}_{wp2}"]
                        )

        # 2. 充装阶段约束
        # 每个组分只能在一个充装设备上加工
        for wp in range(self.num_workpieces):
            for comp in range(self.num_components):
                self.model.linear_constraints.add(
                    lin_expr=[[
                        [self.vars["inflate_eq"][(wp, comp, eq)] for eq in range(self.num_inflation_eq)],
                        [1.0] * self.num_inflation_eq
                    ]],
                    senses=["E"],
                    rhs=[1.0],
                    names=[f"inflate_eq_unique_{wp}_{comp}"]
                )

        # 充装必须在清洗完成后开始
        for wp in range(self.num_workpieces):
            for comp in range(self.num_components):
                self.model.linear_constraints.add(
                    lin_expr=[[
                        [self.vars["inflate_end"][(wp, comp)], self.vars["clean_end"][wp]],
                        [1.0, -1.0]
                    ]],
                    senses=["G"],
                    rhs=[self.inflation_time[wp][comp]],
                    names=[f"inflate_after_clean_{wp}_{comp}"]
                )

        # 3. 摇匀阶段约束（必须在所有充装完成后）
        for wp in range(self.num_workpieces):
            inflate_ends = [self.vars["inflate_end"][(wp, comp)] for comp in range(self.num_components)]
            self.model.linear_constraints.add(
                lin_expr=[[
                    [self.vars["shake_end"][wp]] + inflate_ends,
                    [1.0] + [-1.0] * self.num_components
                ]],
                senses=["G"],
                rhs=[self.shake_time[wp]],
                names=[f"shake_after_inflate_{wp}"]
            )

        # 4. 分析阶段约束（必须在摇匀完成后）
        for wp in range(self.num_workpieces):
            self.model.linear_constraints.add(
                lin_expr=[[
                    [self.vars["analyze_end"][wp], self.vars["shake_end"][wp]],
                    [1.0, -1.0]
                ]],
                senses=["G"],
                rhs=[max(self.analysis_time[wp])],  # 简化：使用最大分析时间
                names=[f"analyze_after_shake_{wp}"]
            )

        # 5. 总完工时间约束
        all_ends = (
                self.vars["clean_end"] +
                [self.vars["inflate_end"][(wp, comp)] for wp in range(self.num_workpieces) for comp in
                 range(self.num_components)] +
                self.vars["shake_end"] +
                self.vars["analyze_end"]
        )
        self.model.linear_constraints.add(
            lin_expr=[[
                [self.vars["makespan"]] + all_ends,
                [1.0] + [-1.0] * len(all_ends)
            ]],
            senses=["G"],
            rhs=[0.0],
            names=["makespan_def"]
        )

    def _set_objective(self):
        """设置目标函数（最小化总完工时间）"""
        self.model.objective.set_linear([(self.vars["makespan"], 1.0)])
        self.model.objective.set_sense(self.model.objective.sense.minimize)

    def _extract_solution(self):
        """从CPLEX解中提取调度方案"""
        if self.model.solution.get_status() not in [1, 101]:  # 1:最优解, 101:可行解
            print(f"未找到可行解，状态码: {self.model.solution.get_status()}")
            return

        # 获取解值
        solution = self.model.solution
        makespan = solution.get_values("makespan")
        self.best_time = makespan
        self.iteration_best_times = [makespan]  # 精确算法只有一个解

        # 构建与基类兼容的解结构
        self.best_solution = {
            'clean_order': list(range(self.num_workpieces)),  # 简化处理
            'clean_eq': [0] * self.num_workpieces,
            'inflation_order': [(wp, comp) for wp in range(self.num_workpieces) for comp in range(self.num_components)],
            'inflation_eq': [[0 for _ in range(self.num_components)] for _ in range(self.num_workpieces)],
            'shake_order': list(range(self.num_workpieces)),
            'shake_eq': [0] * self.num_workpieces,
            'analysis_order': list(range(self.num_workpieces)),
            'component_groups': [[0 for _ in range(self.num_components)] for _ in range(self.num_workpieces)],
            'group_test_order': [[0] for _ in range(self.num_workpieces)],
            'analysis_eq': [[[0 for _ in range(self.num_components)] for __ in range(1)] for ___ in
                            range(self.num_workpieces)]
        }

        # 提取设备分配信息
        for wp in range(self.num_workpieces):
            for eq in range(self.num_clean_eq):
                if round(solution.get_values(f"clean_eq_{wp}_{eq}")) == 1:
                    self.best_solution['clean_eq'][wp] = eq
                    break

        for wp in range(self.num_workpieces):
            for comp in range(self.num_components):
                for eq in range(self.num_inflation_eq):
                    if round(solution.get_values(f"inflate_eq_{wp}_{comp}_{eq}")) == 1:
                        self.best_solution['inflation_eq'][wp][comp] = eq
                        break